﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NFit.Core.BaseClasses;
using NFit.Core.Interfaces;
using NFit.Core.IO;
using System.Reflection;
using NFit.Core;
using log4net;
using NFit.Core.Utils;

namespace NFit.Core.DataSources
{
    /// <summary>
    /// Data source that looks within an assembly, and try to find classes decorated with <see cref="SpecificationContainerAttribute"/>
    /// </summary>
    public class AssemblyDataSource : ISpecificationDataSource
    {
        private static ILog Logger = log4net.LogManager.GetLogger(typeof(AssemblyDataSource));

        #region ISpecDataSource Members

        private IEnumerable<SpecificationDefinition> tests = null;

        private List<Type> _containers = new List<Type>();

        /// <summary>
        /// The name of the data source
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// The specification reader for the data source
        /// </summary>
        public ISpecificationReader Reader
        {
            get { return null; }
            set { }
        }

        /// <summary>
        /// Get the list of root specifications
        /// </summary>
        /// <returns></returns>
        public IEnumerable<SpecificationDefinition> GetTestDefinitions()
        {
            Performances.Measure(Logger, "Assembly " + SpecAssembly.FullName + " loaded in {0}ms", ()=>
                {
                    SearchContainers();
                    if (tests == null)
                        tests = GetSpecs();                                                                                                                       
                });
            return tests;
        }

        private void SearchContainers()
        {
            if (SpecAssembly != null)
            {
                Logger.Debug("Assembly datasource inspecting " + SpecAssembly.FullName);
                Type[] types = SpecAssembly.GetTypes();
                foreach (Type t in types)
                {
                    if (t.IsSubclassOf(typeof(SpecificationDefinition)))
                    {
                        object[] attr = t.GetCustomAttributes(typeof(IgnoreSpecificationAttribute), true);
                        if (attr == null || attr.Length == 0)
                        {
                            Logger.Debug("Specification found " + t.FullName);
                            _containers.Add(t);
                        }
                    }
                    
                }
            }
            else
            {
                Logger.Debug("Assembly datasource not initialized");
            }
        }

        private bool IsASuite(Type t)
        {
            string[] tokens = t.FullName.Split('.');
            foreach(string token in tokens)
            {
                if (token.EndsWith("Suite"))
                {
                    return true;
                }
            }
            return false;
        }

        private IEnumerable<SpecificationDefinition> GetSpecs()
        {
            List<Type> roots = _containers.FindAll(t => !IsASuite(t));

            foreach (Type t in roots)
            {
                yield return ProcessType(t);
            }
        }

        private SpecificationDefinition ProcessType(Type specType)
        {
            _containers.Remove(specType);
            SpecificationDefinition instance = (SpecificationDefinition)specType.Assembly.CreateInstance(specType.FullName);
            instance.Initialize();
            if (!instance.HasName)
                instance.Name = specType.FullName;

            string childSpecsPrefix = specType.FullName + "Suite.";
            int length = childSpecsPrefix.Length;

            List<Type> childs =
                _containers.FindAll(t => t.FullName.StartsWith(childSpecsPrefix) && t.FullName.LastIndexOf('.') <= length);
            foreach(Type t in childs)
            {
                instance.AddChildSpec(ProcessType(t));
            }
            return instance;
        }        

        /// <summary>
        /// The assembly name for the assembly that contains specifications
        /// </summary>
        public string AssemblyName { get; set; }

        private Assembly _specAssembly;
        /// <summary>
        /// The assembly containing specifications
        /// </summary>
        public Assembly SpecAssembly
        {
            get
            {
                if (_specAssembly == null && !string.IsNullOrEmpty(this.AssemblyName))
                {
                    _specAssembly = Assembly.Load(this.AssemblyName);
                }
                return _specAssembly;
            }
            set
            {
                _specAssembly = value;
            }
        }

        #endregion
    }
}
